Lone Runner by François Vander Linden


This next entry for Roby's "Fancy Screen Stuff 2-liner contest" is dedicated to the memory of Doug E. Smith, creator of the most excellent Lode Runner.
This program is called "LONE RUNNER" because in 2 lines you can't do much more but to animate the runner.
The program does nothing more but display a running character. Not very fancy you might think (and not very fast !). Except that it's done using bitmap style animation all-written in Applesoft. There's no 6502 helping subroutine, only Applesoft. There are 3 animation frames of 2x8 bytes (WxH -- 48 bytes in total). I've tried to make the animation as smooth as possible and I think the result is not that bad if you don't mind the speed. The 3 frames are actually reduced to 8-bytes high versions of the 3 frames used by Doug E. Smith in Lode Runner.
Instead of creating the animation frames with DATA/READ statements, I used my PRINT/PLOT technique (this saved LOTS of precious characters for the 2-liner), PRINTing (in $400) 2 lines of 48 characters representing the 48 bytes to animate.
In the same loop that refactors/rebuilds those 2 lines of text as 48 graphic bytes, I store all the bytes in an Applesoft array (it's faster to retrieve the bytes from an array than PEEKing every byte when animating) and I draw 3 lines of pixels representing the "bricks of the ground" as in Lode Runner. So I'm using one loop instead of three. Again a space saver.
The rest of the code basically display the 3 frames in alternance using page flipping to avoid flicker. I've designed the frames so that the left part of the 2 bytes of each frame erases the left bytes of the previous frame but unfortunately as I flip pages, I have to clean what's on the left side of my second-to-last frame. To do that I directly address and clean the 4 (non-contiguous) dirty bytes using POKEs. As there's nothing else on the screen I have the ability to not care and not check if I'm too near the left side of the screen when I do that cleaning (and for the first loop iteration I'm actually cleaning bytes that are not on the left of the running man).
Here's the full code, followed by a detailed explanation, statement by statement. A DSK will follow. Enjoy !
===========================
0DIMA(47):HOME:B=128:N=8*B:M=N*8:HGR:HGR2:?"01010003000080000606030L0307040000000N09L8L8N898N8N8K8L88888L9J88888L8J88K8K898N":VTAB9:?"0L030000":?"88898J8J":FORZ=0TO143:Q=Z/3:K=Q<40:COLOR=SCRN(Q*K,2+S):PLOTK*Q,1+S*K:S=16-S:A(Q)=PEEK(N+Q)
1D=8360+K*Q+Y*N:POKED,243:POKED+M,243:Y=(Y+1)*(Y<2):NEXT:FORX=40TO78:FORF=0TO2:D=M+M*P+X:A=D+2047-X*(F>0):POKEA,B:POKEA+N,B:POKEA+N*4,B:POKEA+N*5,B:FORL=0TO7:A=D+L*N:I=F*16+L*2:POKEA,A(I):POKEA+1,A(I+1):NEXT:POKE49236+P,0:P=1-P:NEXTF,X
===========================
0 DIM A(47): REM this array will store the 48 animation bytes
1 HOME : REM clear the text screen. We don't really need to clear the screen. Instead, of VTAB 1 should be enough but it's one character longer so ...
2 B=128 : N=8*B : M=N*8 : REM a few recurring values. This not only saves space but, also, storing these values in variables is faster than using the actual value in code. We have B=128, N=1024 and M=8192
3 HGR : HGR2 : REM clear boths Hires pages. We clear page 2 last because, this way, as we're going to use page flipping, we don't need to POKE the soft switch to display full graphics.
4 ? "01010003000080000606030L0307040000000N09L8L8N898N8N8K8L88888L9J88888L8J88K8K898N": REM these are the first 40 bytes (out of 48) "encoded" .. the first 40 characters are the low nibble of each byte, while the last 40 are the high nibble of each byte. Also, "hex values" A-F are represented by characters "J-O". These characters will occupy line 1 & 2 of the text page. I have explained the PRINT/PLOT technique in previous submissions, so I won't go through it again.
5 VTAB 9 : REM While text line 1 goes from $400 to $427, line 9 begins in $428 and we need all the bytes to be sequential otherwise when we PEEK those bytes later (to store them in the A() array we would need to adjust our peeking address.
6 ? "0L030000" : ? "88898J8J" : REM these are the last 8-bytes using the same encoding scheme as explained above.
7 FOR Z = 0 TO 143: REM this is the main setup loop. It will repeat 144 times, that is 3 times 48 bytes. One loop of 48 iterations should have been enough to PRINT/PLOT the 48 bytes but we need to deal with the fact that the last 8 bytes are on text line 9, the fact that we want to store the bytes in an Applesoft array and also the fact that we need to display 3 lines of bricks. In order to save space, all these loops have tucked into one. Three times longer because of the three lines of bricks.
8 Q = Z/3: REM Q (as integer) will increase every 3 iteration
9 K = Q<40: REM K indicates if we have read more than the 40 first bytes (we have had more than 3x40 iterations of the loop)
10 COLOR = SCRN(Q*K, 2+S) : REM we get the color of the pixel in position Q (zero if above 40), either of line 2 of lores (line 2 of text) or line 2+S of lores (S flip-flops between 0 and 16)
11 PLOT K*Q , 1 + S*K : REM we plot that color in position Q (zero if above 40), either in line 1 of lores (line 1 of text) or line 1+S of lores. Actually, several "meaningless" points are going to be plotted in (0,1) once we have passed the first 40 bytes. But it doesn't matter, at this stage we have already saved what was in that position.
12 S = 16 - S : REM we alternate S from 0 to 16 and vice-versa
13 A(Q) = PEEK(N+Q) : REM we read the byte in offset Q from 8192 (=N=$400)
14 D = 8360 + K*Q + Y*N : REM time to draw bricks. We compute the byte address. 8360 is line 72 of hires page 1. K*Q is the offset (or zero) while Y*N represent an additional Y-axis offset (Y goes from 0 to 2)
15 POKE D, 243 : REM we poke 243 in the computed address. 243 will display a dashed line.
16 POKE D+M, 243 : REM we now poke the same value but in page 2 (8192 bytes later).
17 Y = (Y+1)*(Y<2) : REM this simple equation will add 1 to Y if Y is below 2 or reset it to zero otherwise, meaning Y will vary from 0 to 2.
18 NEXT: REM and we loop ...
19 FOR X = 40 TO 78: REM the main animation loop ... offset X is actually from byte 0 (left of the screen) to byte 39 but as we are drawing the runner in line 64, it's actually from 8192+40, so we integrate 40 directly in X.
20 FOR F = 0 TO 2: REM from frame 0 to 2
21 D = M + M*P + X: REM we compute a base Y address: 8192 + another 8192 for page 2 (when P=1 instead of 0) + X offset
22 A = D + 2047 - X*(F>0) : REM the next lines will erase the dirty bytes left by the second-to-last frame. The base address of the first dirty byte is two lines below (that's +2048) minus 1 before we want the previous X offset (total is 2047). Notice that if we are not on frame 0, we cancel the original X offset. This means that if we are not on frame 0, the address will be 8192+2047 or 16384+2047 which is somewhere on the first 8 lines of the hires page but as we are going to draw black pixels, it won't be visible. This is specific to 2-liners and should be avoided in "regular" programming. Actually I could have used a POKE 184 to skip those POKEs when not needed but doesn't fit one the line.
23 POKE A,B : REM will clear that first (supposed) dirty byte (B=128 so it's actually 7 pixels of color black #2)
24 POKE A+N,B: REM the next byte to clear is one line below (+1024)
25 POKE A+N*4,B: REM the next byte to clear is 4 lines below
26 POKE A+N*5,B: REM and the last byte is 5 lines below.
27 FOR L = 0 TO 7: REM now we draw the 8 lines of each frame
28 A = D + L*N : REM the starting address for the current byte to draw depends on the line L .. an additional 1024 is needed per line.
29 I = F*16 + L*2: REM this is the offset of the byte in the array ...
30 POKE A, A(I) : REM we poke the leftmost byte
31 POKE A+1, A(I+1): REM then the righmost one ..
32 NEXT : REM loop
33 POKE 49236+P, 0 : REM page flip
34 P = 1 - P: REM now working on the other page ..
35 NEXT F,X: REM loop as needed ....